Imports System.Collections.ObjectModel

<Serializable()> _
Public Class CriteriaGroup
    Implements ICriteriaComponent
    Implements ICriteriaContainer

    Protected mCriteria As List(Of ICriteriaComponent)
    Protected mBooleanOps As List(Of BooleanOperator)
    Protected mNegate As Boolean = False
    Protected mDefaultOp As BooleanOperator = BooleanOperator.And

    Public Sub New()
        Me.New(False)
    End Sub

    Public Sub New(IsTopLevel As Boolean)
        mCriteria = New Generic.List(Of ICriteriaComponent)
        mBooleanOps = New Generic.List(Of BooleanOperator)
        mDefaultOp = BooleanOperator.And
        RenderParens = True
        mIsTopLevel = IsTopLevel
    End Sub

    Public Property DefaultOperator() As BooleanOperator
        Get
            Return mDefaultOp
        End Get
        Set(ByVal value As BooleanOperator)
            If value = BooleanOperator.[Default] Then
                Throw New InvalidOperationException("You may not use BooleanOperators.OpDefault as the Default Operator.")
            End If
            mDefaultOp = value
        End Set
    End Property

    Public Function GetText() As String Implements ICriteriaComponent.GetText
        Return GetText(QueryOptions.DefaultOptions)
    End Function
    Public Function GetText(Options As QueryOptions) As String Implements ICriteriaComponent.GetText
        Dim Query As String = ""
        If Me.ShouldRender Then
            If mNegate Then Query &= "NOT "

            If Not mIsTopLevel AndAlso (RenderParens OrElse Options.AlwaysWrapGroups) Then Query &= "("

            'Only use criteria that should render
            Dim RenderList As New List(Of ICriteriaComponent)
            For Each Criteria As ICriteriaComponent In mCriteria
                If Criteria.HasContent Then
                    RenderList.Add(Criteria)
                End If
            Next

            Dim OpCounter As Integer = 0
            For Each Criteria As ICriteriaComponent In RenderList
                If OpCounter >= 1 Then
                    Query &= GetOpText(mBooleanOps(OpCounter))
                End If

                If TypeOf Criteria Is ICriteriaContainer Then
                    Dim Container As ICriteriaContainer = CType(Criteria, ICriteriaContainer)
                    If mCriteria.Count <= 1 Then
                        Container.RenderParens = False
                    End If
                End If
                Query &= Criteria.GetText(Options)

                OpCounter += 1
            Next

            If Not mIsTopLevel AndAlso (RenderParens OrElse Options.AlwaysWrapGroups) Then Query &= ")"
        End If
        Return Query
    End Function

    Private Function GetOpText(ByVal Op As BooleanOperator) As String
        Dim Ret As String = ""
        Select Case Op
            Case BooleanOperator.And
                Ret = " AND "
            Case BooleanOperator.Or
                Ret = " OR "
            Case BooleanOperator.[Default]
                Ret = GetOpText(mDefaultOp)
            Case Else
                Throw New InvalidOperationException("Invalid BooleanOperator.")
        End Select
        Return Ret
    End Function

    Public Sub AddCriteria(ByVal Criteria As ICriteriaComponent) Implements ICriteriaContainer.AddCriteria
        AddCriteria(Criteria, BooleanOperator.[Default])
    End Sub
    Public Sub AddCriteria(ByVal Criteria As IEnumerable(Of ICriteriaComponent)) Implements ICriteriaContainer.AddCriteria
        For Each Crit As ICriteriaComponent In Criteria
            AddCriteria(Crit, BooleanOperator.[Default])
        Next
    End Sub
    Public Sub AddCriteria(ByVal Criteria As ICriteriaComponent, ByVal Op As BooleanOperator) Implements ICriteriaContainer.AddCriteria
        If Criteria Is Nothing Then
            Throw New InvalidOperationException("You may not add Nothing as a criteria.")
        End If
        mCriteria.Add(Criteria)
        mBooleanOps.Add(Op)
    End Sub
    Public Function AddCriteria(LeftExpression As String, CriteriaOp As Operation, RightExpression As String) As Criteria Implements ICriteriaContainer.AddCriteria
        Return AddCriteria(LeftExpression, CriteriaOp, RightExpression, BooleanOperator.[Default])
    End Function
    Public Function AddCriteria(LeftExpression As String, CriteriaOp As Operation, RightExpression As String, Op As BooleanOperator) As Criteria Implements ICriteriaContainer.AddCriteria
        Return AddCriteria(LeftExpression, CriteriaOp, RightExpression, Op, ExpressionType.Auto)
    End Function
    Public Function AddCriteria(LeftExpression As String, CriteriaOp As Operation, RightExpression As String, Op As BooleanOperator, RightOperandType As ExpressionType) As Criteria
        Dim Criteria As New Criteria(LeftExpression, CriteriaOp, RightExpression)
        Criteria.RightExpressionType = RightOperandType
        mCriteria.Add(Criteria)
        mBooleanOps.Add(Op)
        Return Criteria
    End Function

    Public Sub RemoveCriteria(Criteria As ICriteriaComponent)
        mCriteria.Remove(Criteria)
    End Sub

    Public ReadOnly Property ShouldRender() As Boolean Implements ICriteriaComponent.HasContent
        Get
            For Each Criteria As ICriteriaComponent In mCriteria
                If Criteria.HasContent Then
                    Return True
                End If
            Next
            Return False
        End Get
    End Property

    Public ReadOnly Property CriteriaCount As Integer Implements ICriteriaContainer.CriteriaCount
        Get
            Return mCriteria.Count
        End Get
    End Property

    Public Property RenderParens As Boolean Implements ICriteriaContainer.RenderParens

    Private mIsTopLevel As Boolean
    Public ReadOnly Property IsTopLevel As Boolean
        Get
            Return mIsTopLevel
        End Get
    End Property

    Public Function Criteria() As System.Collections.Generic.IEnumerable(Of ICriteriaComponent) Implements ICriteriaContainer.Criteria
        Return mCriteria
    End Function

    Public Sub ClearCriteria() Implements ICriteriaContainer.ClearCriteria
        mCriteria.Clear()
    End Sub
End Class

Public Enum BooleanOperator As Integer
    'OpAnd = 0
    'OpOr = 1
    [And] = 0
    [Or] = 1
    [Default] = 2
End Enum